1 package edu.jiangxin.apktoolbox.file.password.recovery;
2
3 import edu.jiangxin.apktoolbox.file.password.recovery.category.CategoryFactory;
4 import edu.jiangxin.apktoolbox.file.password.recovery.category.CategoryType;
5 import edu.jiangxin.apktoolbox.file.password.recovery.category.ICategory;
6 import edu.jiangxin.apktoolbox.file.password.recovery.checker.*;
7 import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdParty7ZipChecker;
8 import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdPartyRarChecker;
9 import edu.jiangxin.apktoolbox.file.password.recovery.checker.thirdparty.ThirdPartyWinRarChecker;
10 import edu.jiangxin.apktoolbox.swing.extend.EasyPanel;
11 import edu.jiangxin.apktoolbox.swing.extend.filepanel.FilePanel;
12 import edu.jiangxin.apktoolbox.utils.Constants;
13 import org.apache.commons.collections4.CollectionUtils;
14 import org.apache.commons.io.FilenameUtils;
15 import org.apache.commons.lang3.ArrayUtils;
16 import org.apache.commons.lang3.StringUtils;
17
18 import javax.swing.*;
19 import java.awt.*;
20 import java.awt.datatransfer.Clipboard;
21 import java.awt.datatransfer.StringSelection;
22 import java.io.File;
23 import java.io.Serial;
24 import java.text.NumberFormat;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.HashSet;
28 import java.util.List;
29 import java.util.Set;
30 import java.util.concurrent.ExecutorService;
31 import java.util.concurrent.Executors;
32
33 public final class RecoveryPanel extends EasyPanel {
34 @Serial
35 private static final long serialVersionUID = 1L;
36
37 private JPanel optionPanel;
38
39 private FilePanel recoveryFilePanel;
40
41 private JTabbedPane categoryTabbedPane;
42
43 private JPanel bruteForceCategoryPanel;
44
45 private JPanel dictionaryCategoryPanel;
46
47 private FilePanel dictionaryFilePanel;
48
49 private CategoryType currentCategoryType = CategoryType.UNKNOWN;
50
51 private JPanel operationPanel;
52
53 private JCheckBox numberCheckBox;
54 private JCheckBox lowercaseLetterCheckBox;
55 private JCheckBox uppercaseLetterCheckBox;
56
57 private JCheckBox userIncludedCheckBox;
58
59 private JTextField userIncludedTextField;
60
61 private JCheckBox userExcludedCheckBox;
62
63 private JTextField userExcludedTextField;
64
65 private JSpinner minSpinner;
66 private JSpinner maxSpinner;
67
68 private JCheckBox isUseMultiThreadCheckBox;
69
70 private JProgressBar progressBar;
71
72 private JComboBox<FileChecker> checkerTypeComboBox;
73
74 private transient FileChecker currentFileChecker;
75
76 private JButton startButton;
77 private JButton stopButton;
78
79 private JLabel currentStateLabel;
80
81 private JLabel currentPasswordLabel;
82
83 private JLabel currentSpeedLabel;
84
85 private int passwordTryCount = 0;
86
87 private NumberFormat numberFormat;
88
89 private State currentState = State.IDLE;
90
91 private Timer timer;
92
93 private final transient ExecutorService startExecutorService = Executors.newFixedThreadPool(1);
94
95 private final transient ExecutorService stopExecutorService = Executors.newFixedThreadPool(1);
96
97 public RecoveryPanel() {
98 super();
99 initBase();
100 }
101
102 private void initBase() {
103 numberFormat = NumberFormat.getPercentInstance();
104 numberFormat.setMinimumFractionDigits(3);
105 }
106
107 @Override
108 public void initUI() {
109 BoxLayout boxLayout = new BoxLayout(this, BoxLayout.Y_AXIS);
110 setLayout(boxLayout);
111
112 createOptionPanel();
113 add(optionPanel);
114 add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
115
116 createOperationPanel();
117 add(operationPanel);
118
119 timer = new Timer(1000, e -> {
120 if (currentState == State.WORKING) {
121 int currentValue = progressBar.getValue();
122 int speed = currentValue - passwordTryCount;
123 passwordTryCount = currentValue;
124 currentSpeedLabel.setText("Speed: " + speed + " passwords/s");
125 }
126 });
127 }
128
129 private void createOptionPanel() {
130 optionPanel = new JPanel();
131 optionPanel.setLayout(new BoxLayout(optionPanel, BoxLayout.Y_AXIS));
132
133 checkerTypeComboBox = new JComboBox<>();
134 checkerTypeComboBox.addItem(new ThirdParty7ZipChecker());
135 checkerTypeComboBox.addItem(new ThirdPartyWinRarChecker());
136 checkerTypeComboBox.addItem(new ThirdPartyRarChecker());
137 checkerTypeComboBox.addItem(new ZipChecker());
138 checkerTypeComboBox.addItem(new RarChecker());
139 checkerTypeComboBox.addItem(new SevenZipChecker());
140 checkerTypeComboBox.addItem(new PdfChecker());
141 checkerTypeComboBox.addItem(new XmlBasedOfficeChecker());
142 checkerTypeComboBox.addItem(new BinaryOfficeChecker());
143 checkerTypeComboBox.setSelectedIndex(0);
144 checkerTypeComboBox.addItemListener(e -> {
145 FileChecker fileChecker = (FileChecker) e.getItem();
146 if (fileChecker == null) {
147 logger.error("fileChecker is null");
148 return;
149 }
150 recoveryFilePanel.setDescriptionAndFileExtensions(
151 fileChecker.getFileDescription(), fileChecker.getFileExtensions());
152 });
153
154 FileChecker fileChecker = (FileChecker) checkerTypeComboBox.getSelectedItem();
155 if (fileChecker == null) {
156 logger.error("fileChecker is null");
157 return;
158 }
159
160 recoveryFilePanel = new FilePanel("Choose Recovery File");
161 recoveryFilePanel.initialize();
162 recoveryFilePanel.setDescriptionAndFileExtensions(fileChecker.getFileDescription(), fileChecker.getFileExtensions());
163
164 categoryTabbedPane = new JTabbedPane();
165
166 createBruteForcePanel();
167 categoryTabbedPane.addTab("Brute Force", null, bruteForceCategoryPanel, "Brute Force");
168 categoryTabbedPane.setSelectedIndex(0);
169
170 createDictionaryPanel();
171 categoryTabbedPane.addTab("Dictionary", null, dictionaryCategoryPanel, "Dictionary");
172
173 progressBar = new JProgressBar();
174 progressBar.setStringPainted(true);
175 String text = numberFormat.format(0);
176 progressBar.setString(text);
177
178 optionPanel.add(checkerTypeComboBox);
179 optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
180 optionPanel.add(recoveryFilePanel);
181 optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
182 optionPanel.add(categoryTabbedPane);
183 optionPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
184 optionPanel.add(progressBar);
185 }
186
187 private void createBruteForcePanel() {
188 bruteForceCategoryPanel = new JPanel();
189
190 JPanel topLevelPanel = new JPanel();
191 topLevelPanel.setLayout(new BoxLayout(topLevelPanel, BoxLayout.Y_AXIS));
192 bruteForceCategoryPanel.add(topLevelPanel);
193
194 JPanel charsetPanel = new JPanel();
195 charsetPanel.setLayout(new BoxLayout(charsetPanel, BoxLayout.X_AXIS));
196
197 JPanel passwordLengthPanel = new JPanel();
198 passwordLengthPanel.setLayout(new BoxLayout(passwordLengthPanel, BoxLayout.X_AXIS));
199
200 topLevelPanel.add(charsetPanel);
201 topLevelPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
202 topLevelPanel.add(passwordLengthPanel);
203
204 numberCheckBox = new JCheckBox("Number");
205 numberCheckBox.setSelected(true);
206 lowercaseLetterCheckBox = new JCheckBox("Lowercase Letter");
207 uppercaseLetterCheckBox = new JCheckBox("Uppercase Letter");
208 userIncludedCheckBox = new JCheckBox("User-defined");
209 userIncludedTextField = new JTextField(10);
210 userExcludedCheckBox = new JCheckBox("User-excluded");
211 userExcludedTextField = new JTextField(10);
212
213
214 charsetPanel.add(numberCheckBox);
215 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
216 charsetPanel.add(lowercaseLetterCheckBox);
217 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
218 charsetPanel.add(uppercaseLetterCheckBox);
219 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
220 charsetPanel.add(userIncludedCheckBox);
221 charsetPanel.add(userIncludedTextField);
222 charsetPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
223 charsetPanel.add(userExcludedCheckBox);
224 charsetPanel.add(userExcludedTextField);
225
226 JLabel minLabel = new JLabel("Minimum Length: ");
227 JLabel maxLabel = new JLabel("Maximum Length: ");
228
229 minSpinner = new JSpinner();
230 minSpinner.setModel(new SpinnerNumberModel(1, 1, 9, 1));
231 minSpinner.setToolTipText("Minimum Length");
232
233 maxSpinner = new JSpinner();
234 maxSpinner.setModel(new SpinnerNumberModel(6, 1, 9, 1));
235 maxSpinner.setToolTipText("Maximum Length");
236
237 passwordLengthPanel.add(minLabel);
238 passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
239 passwordLengthPanel.add(minSpinner);
240 passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
241 passwordLengthPanel.add(maxLabel);
242 passwordLengthPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
243 passwordLengthPanel.add(maxSpinner);
244 }
245
246 private void createDictionaryPanel() {
247 dictionaryCategoryPanel = new JPanel();
248
249 JPanel topLevelPanel = new JPanel();
250 topLevelPanel.setLayout(new BoxLayout(topLevelPanel, BoxLayout.Y_AXIS));
251 dictionaryCategoryPanel.add(topLevelPanel);
252
253 dictionaryFilePanel = new FilePanel("Choose Dictionary File");
254 dictionaryFilePanel.initialize();
255 dictionaryFilePanel.setDescriptionAndFileExtensions("*.dic;*.txt", new String[]{"dic", "txt"});
256
257 JPanel threadPanel = new JPanel();
258 threadPanel.setLayout(new BoxLayout(threadPanel, BoxLayout.X_AXIS));
259
260 topLevelPanel.add(dictionaryFilePanel);
261 topLevelPanel.add(Box.createVerticalStrut(Constants.DEFAULT_Y_BORDER));
262 topLevelPanel.add(threadPanel);
263
264 isUseMultiThreadCheckBox = new JCheckBox("Use Multi-thread");
265 isUseMultiThreadCheckBox.setSelected(true);
266
267 threadPanel.add(isUseMultiThreadCheckBox);
268 }
269
270 private void createOperationPanel() {
271 operationPanel = new JPanel();
272 operationPanel.setLayout(new BoxLayout(operationPanel, BoxLayout.X_AXIS));
273
274 startButton = new JButton("Start");
275 stopButton = new JButton("Stop");
276 currentStateLabel = new JLabel("State: IDLE");
277 currentPasswordLabel = new JLabel("Trying: ");
278 currentSpeedLabel = new JLabel("Speed: 0 passwords/s");
279
280 operationPanel.add(startButton);
281 operationPanel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
282 operationPanel.add(stopButton);
283 operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER));
284 operationPanel.add(currentStateLabel);
285 operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER));
286 operationPanel.add(currentPasswordLabel);
287 operationPanel.add(Box.createHorizontalStrut(2 * Constants.DEFAULT_X_BORDER));
288 operationPanel.add(currentSpeedLabel);
289 operationPanel.add(Box.createHorizontalGlue());
290
291 startButton.addActionListener(e -> startExecutorService.submit(this::onStart));
292 stopButton.addActionListener(e -> stopExecutorService.submit(this::onStop));
293
294 setCurrentState(State.IDLE);
295 }
296
297 private void onStart() {
298 FileChecker fileChecker = (FileChecker) checkerTypeComboBox.getSelectedItem();
299 if (fileChecker == null) {
300 JOptionPane.showMessageDialog(this, "fileChecker is null!");
301 return;
302 }
303 if (!fileChecker.prepareChecker()) {
304 JOptionPane.showMessageDialog(this, "onStart failed: Checker condition does not been prepared!");
305 return;
306 }
307
308 File selectedFile = recoveryFilePanel.getFile();
309 if (!selectedFile.isFile()) {
310 JOptionPane.showMessageDialog(this, "file is not a file!");
311 return;
312 }
313
314 String extension = FilenameUtils.getExtension(recoveryFilePanel.getFile().getName());
315 if (!ArrayUtils.contains(fileChecker.getFileExtensions(), StringUtils.toRootLowerCase(extension))) {
316 JOptionPane.showMessageDialog(this, "invalid file!");
317 return;
318 }
319
320 fileChecker.attachFile(selectedFile);
321 currentFileChecker = fileChecker;
322
323 Component selectedPanel = categoryTabbedPane.getSelectedComponent();
324 if (selectedPanel.equals(bruteForceCategoryPanel)) {
325 currentCategoryType = CategoryType.BRUTE_FORCE;
326 } else if (selectedPanel.equals(dictionaryCategoryPanel)) {
327 if (isUseMultiThreadCheckBox.isSelected()) {
328 currentCategoryType = CategoryType.DICTIONARY_MULTI_THREAD;
329 } else {
330 currentCategoryType = CategoryType.DICTIONARY_SINGLE_THREAD;
331 }
332 } else {
333 currentCategoryType = CategoryType.UNKNOWN;
334 }
335 logger.info("onStart: {}", currentCategoryType);
336 if (currentCategoryType == CategoryType.UNKNOWN) {
337 JOptionPane.showMessageDialog(this, "onStart failed: Invalid category!");
338 return;
339 }
340 setCurrentState(State.WORKING);
341 passwordTryCount = 0;
342 timer.start();
343 ICategory category = CategoryFactory.getCategoryInstance(currentCategoryType);
344 category.start(this);
345 if (currentState == State.WORKING) {
346 onStop();
347 }
348 }
349
350 private void onStop() {
351 logger.info("onStop: currentState: {}, currentCategoryType: {}", currentState, currentCategoryType);
352 if (currentState != State.WORKING) {
353 logger.error("onStop failed: Not in working state!");
354 return;
355 }
356 if (currentCategoryType == CategoryType.UNKNOWN) {
357 logger.error("onStop failed: Invalid category!");
358 return;
359 }
360 timer.stop();
361 setCurrentState(State.STOPPING);
362 ICategory category = CategoryFactory.getCategoryInstance(currentCategoryType);
363 category.cancel();
364 setCurrentState(State.IDLE);
365 currentCategoryType = CategoryType.UNKNOWN;
366 }
367
368 public void showResultWithDialog(String password) {
369 if (password == null) {
370 logger.error("Can not find password");
371 JOptionPane.showMessageDialog(this, "Can not find password");
372 } else {
373 logger.info("Find out the password: {}", password);
374 JPanel panel = createPasswordShowPanel(password);
375 JOptionPane.showMessageDialog(this, panel, "Find out the password", JOptionPane.INFORMATION_MESSAGE);
376 }
377 }
378
379 private static JPanel createPasswordShowPanel(String password) {
380 JPanel panel = new JPanel();
381 panel.setLayout(new BoxLayout(panel, BoxLayout.X_AXIS));
382 JTextField textField = new JTextField(password);
383 textField.setEditable(false);
384 textField.setColumns(20);
385 JButton button = new JButton("Copy");
386 button.addActionListener(e -> {
387 StringSelection stringSelection = new StringSelection(password);
388 Clipboard clipboard = Toolkit.getDefaultToolkit().getSystemClipboard();
389 clipboard.setContents(stringSelection, null);
390 });
391 panel.add(textField);
392 panel.add(Box.createHorizontalStrut(Constants.DEFAULT_X_BORDER));
393 panel.add(button);
394 return panel;
395 }
396
397 public void setCurrentState(State currentState) {
398 SwingUtilities.invokeLater(() -> {
399 this.currentState = currentState;
400 if (currentStateLabel != null) {
401 currentStateLabel.setText("State: " + currentState.toString());
402 }
403 if (currentState == State.WORKING) {
404 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
405 startButton.setEnabled(false);
406 stopButton.setEnabled(true);
407 updateUiComponent(false);
408 } else if (currentState == State.STOPPING) {
409 setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
410 startButton.setEnabled(false);
411 stopButton.setEnabled(false);
412 updateUiComponent(false);
413 } else if (currentState == State.IDLE) {
414 setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
415 startButton.setEnabled(true);
416 stopButton.setEnabled(false);
417 updateUiComponent(true);
418 }
419 });
420 }
421
422 private void updateUiComponent(boolean enable) {
423 for (Component component : getComponents(optionPanel)) {
424 component.setEnabled(enable);
425 }
426 }
427
428 private Component[] getComponents(Component container) {
429 List<Component> list;
430
431 try {
432 list = new ArrayList<>(Arrays.asList(
433 ((Container) container).getComponents()));
434 for (int index = 0; index < list.size(); index++) {
435 list.addAll(Arrays.asList(getComponents(list.get(index))));
436 }
437 } catch (ClassCastException e) {
438 list = new ArrayList<>();
439 }
440
441 return list.toArray(new Component[0]);
442 }
443
444 public State getCurrentState() {
445 return currentState;
446 }
447
448 public void resetProgressMaxValue(int maxValue) {
449 SwingUtilities.invokeLater(() -> {
450 progressBar.setMaximum(maxValue);
451 setProgressBarValue(0);
452 });
453 }
454
455 public void increaseProgressBarValue() {
456 SwingUtilities.invokeLater(() -> setProgressBarValue(progressBar.getValue() + 1));
457 }
458
459 private void setProgressBarValue(int value) {
460 int properValue = Math.min(value, progressBar.getMaximum());
461 progressBar.setValue(properValue);
462 String text = numberFormat.format(((double) properValue) / progressBar.getMaximum());
463 progressBar.setString(text);
464 }
465
466 public void setCurrentPassword(String password) {
467 SwingUtilities.invokeLater(() -> currentPasswordLabel.setText("Trying: " + password));
468 }
469
470 public String getCharset() {
471 Set<Character> charsetSet = new HashSet<>();
472 if (numberCheckBox.isSelected()) {
473 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("0123456789".toCharArray()));
474 }
475 if (lowercaseLetterCheckBox.isSelected()) {
476 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("abcdefghijklmnopqrstuvwxyz".toCharArray()));
477 }
478 if (uppercaseLetterCheckBox.isSelected()) {
479 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject("ABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray()));
480 }
481 if (userIncludedCheckBox.isSelected()) {
482 CollectionUtils.addAll(charsetSet, ArrayUtils.toObject(userIncludedTextField.getText().toCharArray()));
483 }
484 if (userExcludedCheckBox.isSelected()) {
485 for (char ch : userExcludedTextField.getText().toCharArray()) {
486 charsetSet.remove(ch);
487 }
488 }
489 return String.valueOf(ArrayUtils.toPrimitive(charsetSet.toArray(new Character[0])));
490 }
491
492 public File getDictionaryFile() {
493 return dictionaryFilePanel.getFile();
494 }
495
496 public int getMinLength() {
497 return (Integer) minSpinner.getValue();
498 }
499
500 public int getMaxLength() {
501 return (Integer) maxSpinner.getValue();
502 }
503
504 public FileChecker getCurrentFileChecker() {
505 return currentFileChecker;
506 }
507 }